package com.netflexitysolutions.amazonws.sdb.orm.metadata;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.beanutils.ConstructorUtils;

/**
 * @author Max Fedorov
 *
 * Contains all the objects and attributes that belong to 
 * &lt;class&gt; XML element.
 * 
 * &lt;class&lt; table attribute is the domain name.
 * &lt;class&lt; discriminator-value attribute is the logical table name if within the same domain.
 */
public class ItemMetadata extends AbstractSimpleDBMetadata{
	
	private IdMetadata id;
    private List<OneToManyMetadata> oneToMany = new ArrayList<OneToManyMetadata>(2);
    private Map<String, OneToManyMetadata> oneToManyByColumn = new TreeMap<String, OneToManyMetadata>();
    private List<ManyToOneMetadata> manyToOne = new ArrayList<ManyToOneMetadata>(2);
    private Map<String, ManyToOneMetadata> manyToOneByColumn = new TreeMap<String, ManyToOneMetadata>();
    private List<ManyToManyMetadata> manyToMany = new ArrayList<ManyToManyMetadata>(2);
    private Map<String, ManyToManyMetadata> manyToManyByColumn = new TreeMap<String, ManyToManyMetadata>();
    private List<S3Metadata> s3 = new ArrayList<S3Metadata>(2);
    private Map<String, S3Metadata> s3ByColumn = new TreeMap<String, S3Metadata>();
    private Map<String, AttributeMetadata> attributes = new TreeMap<String, AttributeMetadata>();
    private Map<String, AttributeMetadata> attributesByColumn = new TreeMap<String, AttributeMetadata>();
	private SimpleDBMapping mapping;
	private String domainName;
	private String logicalName;
	private String logicalNameColumnName = "LN";
	private boolean cachable;
	private String cacheUsage;
	
	/**
	 * @param attribute
	 */
	public void addAttribute(AttributeMetadata attribute) {
		attribute.setItemMetadata(this);
	    this.attributes.put(attribute.getName(), attribute);
	    this.attributesByColumn.put(attribute.getColumnName(), attribute);
	}
    
	/**
	 * @param name
	 * @return
	 */
	public AttributeMetadata getAttributeMetadataByName(String name) {
		assert(name != null);
	    return attributes.get(name);
	}
    
	/**
	 * @param name
	 * @return
	 */
	public AttributeMetadata getAttributeMetadataByColumn(String name) {
		assert(name != null);
	    return attributesByColumn.get(name);
	}
	
    /**
     * @return
     */
    public List<AttributeMetadata> getAttributeMetadata() {
        return Collections.unmodifiableList(new ArrayList<AttributeMetadata>(attributes.values()));
    }
    
    /**
	 * @return the id
	 */
	public IdMetadata getId() {
		return id;
	}

	/**
	 * @param id the id to set
	 */
	public void setId(IdMetadata id) {
		this.id = id;
	}

	/**
	 * @return the oneToMany
	 */
	public List<OneToManyMetadata> getOneToMany() {
		return Collections.unmodifiableList(oneToMany);
	}

	/**
	 * @param oneToMany the oneToMany to set
	 */
	public void addOneToMany(OneToManyMetadata oneToMany) {
		oneToMany.setItemMetadata(this);
		this.oneToMany.add(oneToMany);
		this.oneToManyByColumn.put(oneToMany.getColumnName(), oneToMany);
	}

	/**
	 * @param name
	 * @return
	 */
	public OneToManyMetadata getOneToManyMetadataByColumn(String name) {
		assert(name != null);
	    return oneToManyByColumn.get(name);
	}
	
	/**
	 * @return the manyToOne
	 */
	public List<ManyToOneMetadata> getManyToOne() {
		return Collections.unmodifiableList(manyToOne);
	}

	/**
	 * @param manyToOne the manyToOne to set
	 */
	public void addManyToOne(ManyToOneMetadata manyToOne) {
		manyToOne.setItemMetadata(this);
		this.manyToOne.add(manyToOne);
		this.manyToOneByColumn.put(manyToOne.getColumnName(), manyToOne);
	}

	/**
	 * @param name
	 * @return
	 */
	public ManyToOneMetadata getManyToOneMetadataByColumn(String name) {
		assert(name != null);
	    return manyToOneByColumn.get(name);
	}
	
    /**
	 * @return the manyToMany
	 */
	public List<ManyToManyMetadata> getManyToMany() {
		return Collections.unmodifiableList(manyToMany);
	}

	/**
	 * @param manyToMany the manyToMany to set
	 */
	public void addManyToMany(ManyToManyMetadata manyToMany) {
		manyToMany.setItemMetadata(this);
		this.manyToMany.add(manyToMany);
		this.manyToManyByColumn.put(manyToMany.getColumnName(), manyToMany);
	}

	/**
	 * @param name
	 * @return
	 */
	public ManyToManyMetadata getManyToManyMetadataByColumn(String name) {
		assert(name != null);
	    return manyToManyByColumn.get(name);
	}
	
	/**
	 * @return the s3
	 */
	public List<S3Metadata> getS3() {
		return Collections.unmodifiableList(s3);
	}

	/**
	 * @param s3 the s3 to set
	 */
	public void addS3(S3Metadata s3) {
		s3.setItemMetadata(this);
		this.s3.add(s3);
		this.s3ByColumn.put(s3.getColumnName(), s3);
	}

	/**
	 * @param name
	 * @return
	 */
	public S3Metadata getS3MetadataByColumn(String name) {
		assert(name != null);
	    return s3ByColumn.get(name);
	}
	
	/**
	 * @param collectionMetadata - either one-to-many or many-to-many
	 */
	public void addCollection(CollectionMetadata collectionMetadata) {
		if(collectionMetadata.isManyToMany()){
			ManyToManyMetadata m2m = new ManyToManyMetadata();
			m2m.setClassName(collectionMetadata.getClassName());
			m2m.setColumnName(collectionMetadata.getColumnName());
			m2m.setItemMetadata(this);
			m2m.setLazy(collectionMetadata.isLazy());
			m2m.setLength(collectionMetadata.getLength());
			m2m.setName(collectionMetadata.getName());
			m2m.setRequired(collectionMetadata.isRequired());
			m2m.setTargetColumnName(collectionMetadata.getTargetColumnName());
			m2m.setNormalization(collectionMetadata.getNormalization());
			m2m.setCollectionClazz(collectionMetadata.getCollectionClazz());
			
			this.manyToMany.add(m2m);
			this.manyToManyByColumn.put(m2m.getColumnName(), m2m);
		}
		else if(collectionMetadata.isOneToMany()){
			OneToManyMetadata o2m = new OneToManyMetadata();
			o2m.setClassName(collectionMetadata.getClassName());
			o2m.setColumnName(collectionMetadata.getColumnName());
			o2m.setItemMetadata(this);
			o2m.setLazy(collectionMetadata.isLazy());
			o2m.setLength(collectionMetadata.getLength());
			o2m.setName(collectionMetadata.getName());
			o2m.setRequired(collectionMetadata.isRequired());
			o2m.setInverse(collectionMetadata.isInverse());
			o2m.setNormalization(collectionMetadata.getNormalization());
			o2m.setCollectionClazz(collectionMetadata.getCollectionClazz());
			
			this.oneToMany.add(o2m);
			this.oneToManyByColumn.put(o2m.getColumnName(), o2m);
		}
	}
	
	/**
	 * @return the mapping
	 */
	public SimpleDBMapping getMapping() {
		return mapping;
	}

	/**
	 * @param mapping the mapping to set
	 */
	public void setMapping(SimpleDBMapping mapping) {
		this.mapping = mapping;
	}

	/**
	 * @return the domainName
	 */
	public String getDomainName() {
		return domainName;
	}

	/**
	 * @param domainName the domainName to set
	 */
	public void setDomainName(String domainName) {
		this.domainName = domainName;
	}

	/**
	 * @return the logicalName
	 */
	public String getLogicalName() {
		return logicalName;
	}

	/**
	 * @param logicalName the logicalName to set
	 */
	public void setLogicalName(String logicalName) {
		this.logicalName = logicalName;
	}

	/**
	 * @return the logicalNameColumnName
	 */
	public String getLogicalNameColumnName() {
		return logicalNameColumnName;
	}

	/**
	 * @param logicalNameColumnName the logicalNameColumnName to set
	 */
	public void setLogicalNameColumnName(String logicalNameColumnName) {
		this.logicalNameColumnName = logicalNameColumnName;
	}

	/**
	 * @return the cachable
	 */
	public boolean isCachable() {
		return cachable;
	}

	/**
	 * @param cachable the cachable to set
	 */
	public void setCachable(String junk) {
		this.cachable = true;
	}

	/**
	 * @return the cacheUsage
	 */
	public String getCacheUsage() {
		return cacheUsage;
	}

	/**
	 * @param cacheUsage the cacheUsage to set
	 */
	public void setCacheUsage(String cacheUsage) {
		this.cacheUsage = cacheUsage;
	}

	/**
     * @param <T>
     * @return
     */
    public Object newRecord() {
        try {
            return ConstructorUtils.invokeConstructor(getJavaClass(), new Object[]{});
        }
        catch (Throwable e) {
            throw new MetadataException("Failed to create instance of class " + getClassName());
        }
    }
}
